/* Copyright 2007-2008 dnAnalytics Project.
 *
 * Contributors to this file:
 * Patrick van der Velde
 *
 * This file is part of dnAnalytics.  dnAnalytics is licensed under the 
 * Microsoft Public License. See License.txt for a complete copy of the
 * license.
 */

using System;
using System.Diagnostics;
using NUnit.Framework;
using dnAnalytics.LinearAlgebra.Solvers;
using dnAnalytics.LinearAlgebra;

namespace dnAnalytics.Tests.LinearAlgebra.Solvers
{
    [TestFixture]
    public sealed class ResidualStopCriteriumTest
    {
        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CreateWithNegativeMaximum()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(-0.1);
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void CreateWithIllegalMinimumIterations()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium((int)-1);
            Assert.Fail();
        }

        [Test]
        public void Create()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            Assert.AreEqual(1e-8, criterium.Maximum, "Incorrect maximum");
            Assert.AreEqual(50, criterium.MinimumIterationsBelowMaximum, "Incorrect iteration count");
        }

        [Test]
        public void ResetMaximum()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.ResetMaximumResidualToDefault();
            Assert.AreEqual(ResidualStopCriterium.DefaultMaximumResidual, criterium.Maximum, "Incorrect maximum");
        }

        [Test]
        public void ResetMinimumIterationsBelowMaximum()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.ResetMinimumIterationsBelowMaximumToDefault();
            Assert.AreEqual(ResidualStopCriterium.DefaultMinimumIterationsBelowMaximum, criterium.MinimumIterationsBelowMaximum, "Incorrect iteration count");
        }

        [Test]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void DetermineStatusWithIllegalIterationNumber()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.DetermineStatus(-1, 
                                      new DenseVector(3, 4), 
                                      new DenseVector(3, 5),
                                      new DenseVector(3, 6));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DetermineStatusWithNullSolutionVector()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.DetermineStatus(1, 
                                      null, 
                                      new DenseVector(3, 5),
                                      new DenseVector(3, 6));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DetermineStatusWithNullSourceVector()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.DetermineStatus(1,
                                      new DenseVector(3, 4),
                                      null,
                                      new DenseVector(3, 6));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void DetermineStatusWithNullResidualVector()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.DetermineStatus(1, 
                                      new DenseVector(3, 4),
                                      new DenseVector(3, 5),
                                      null);
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void DetermineStatusWithNonMatchingSolutionVector()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.DetermineStatus(1,
                                      new DenseVector(4, 4),
                                      new DenseVector(3, 4),
                                      new DenseVector(3, 4));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void DetermineStatusWithNonMatchingSourceVector()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.DetermineStatus(1,
                                      new DenseVector(3, 4),
                                      new DenseVector(4, 4),
                                      new DenseVector(3, 4));
            Assert.Fail();
        }

        [Test]
        [ExpectedException(typeof(NotConformableException))]
        public void DetermineStatusWithNonMatchingResidualVector()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-8, 50);
            Assert.IsNotNull(criterium, "There should be a criterium");

            criterium.DetermineStatus(1, 
                                      new DenseVector(3, 4),
                                      new DenseVector(3, 4),
                                      new DenseVector(4, 4));
            Assert.Fail();
        }
        
        [Test]
        public void DetermineStatusWithSourceNaN()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-3, 10);
            Assert.IsNotNull(criterium, "There should be a criterium");

            DenseVector solution = new DenseVector(new double[] { 1, 1, 2 });
            DenseVector source = new DenseVector(new double[] { 1, 1, double.NaN });
            DenseVector residual = new DenseVector(new double[] { 1000, 1000, 2001 });

            criterium.DetermineStatus(5, solution, source, residual);
            Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Should be diverged");
        }

        [Test]
        public void DetermineStatusWithResidualNaN()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-3, 10);
            Assert.IsNotNull(criterium, "There should be a criterium");

            DenseVector solution = new DenseVector(new double[] { 1, 1, 2 });
            DenseVector source = new DenseVector(new double[] { 1, 1, 2 });
            DenseVector residual = new DenseVector(new double[] { 1000, double.NaN, 2001 });

            criterium.DetermineStatus(5, solution, source, residual);
            Assert.IsInstanceOfType(typeof(CalculationDiverged), criterium.Status, "Should be diverged");
        }

        // Bugfix: The unit tests for the BiCgStab solver run with a super simple matrix equation
        //         which converges at the first iteration. The default settings for the
        //         residual stop criterium should be able to handle this.
        [Test]
        public void DetermineStatusWithConvergenceAtFirstIteration()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium();
            Assert.IsNotNull(criterium, "There should be a criterium");

            DenseVector solution = new DenseVector(new double[] { 1, 1, 1 });
            DenseVector source = new DenseVector(new double[] { 1, 1, 1 });
            DenseVector residual = new DenseVector(new double[] { 0, 0, 0 });

            criterium.DetermineStatus(0, solution, source, residual);
            Assert.IsInstanceOfType(typeof(CalculationConverged), criterium.Status, "Should be done");
        }

        [Test]
        public void DetermineStatus()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-3, 10);
            Assert.IsNotNull(criterium, "There should be a criterium");

            // Note that the solution vector isn't actually being used so ...
            DenseVector solution = new DenseVector(new double[] { double.NaN, double.NaN, double.NaN });
            // Set the source values
            DenseVector source = new DenseVector(new double[] { 1.000, 1.000, 2.001 });
            // Set the residual values
            DenseVector residual = new DenseVector(new double[] { 0.001, 0.001, 0.002 });

            criterium.DetermineStatus(5, solution, source, residual);
            Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Should still be running");

            criterium.DetermineStatus(16, solution, source, residual);
            Assert.IsInstanceOfType(typeof(CalculationConverged), criterium.Status, "Should be done");
        }

        [Test]
        public void ResetCalculationState()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-3, 10);
            Assert.IsNotNull(criterium, "There should be a criterium");

            DenseVector solution = new DenseVector(new double[] { 0.001, 0.001, 0.002 });
            DenseVector source = new DenseVector(new double[] { 0.001, 0.001, 0.002 });
            DenseVector residual = new DenseVector(new double[] { 1.000, 1.000, 2.001 });

            criterium.DetermineStatus(5, solution, source, residual);
            Assert.IsInstanceOfType(typeof(CalculationRunning), criterium.Status, "Should be running");

            criterium.ResetToPrecalculationState();
            Assert.IsInstanceOfType(typeof(CalculationIndetermined), criterium.Status, "Should not have started");
        }

        [Test]
        public void Clone()
        {
            ResidualStopCriterium criterium = new ResidualStopCriterium(1e-3, 10);
            Assert.IsNotNull(criterium, "There should be a criterium");

            IIterationStopCriterium clone = criterium.Clone();
            Assert.IsInstanceOfType(typeof(ResidualStopCriterium), clone, "Wrong criterium type");

            ResidualStopCriterium clonedCriterium = clone as ResidualStopCriterium;
            Assert.AreEqual(criterium.Maximum, clonedCriterium.Maximum, "Clone failed");
            Assert.AreEqual(criterium.MinimumIterationsBelowMaximum, clonedCriterium.MinimumIterationsBelowMaximum, "Clone failed");
        }
    }
}
